#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <time.h>

#define BUFF_SIZE   5

void* productor(void*);
void* consumidor(void*);

/*Estructura para manejar el buffer usando pthreads*/
typedef struct {
    int                 buf[BUFF_SIZE]; /*buffer de mensajes*/
    pthread_mutex_t     mutex; /*semaforo para la exclusion mutua*/
    pthread_cond_t      slot_available;    /*variable de condicion para señalizar la liberacion de espacio */
    pthread_cond_t      message_available; /*variable de condicion para señalizar un nuevo mensaje*/
    int                 nextin; /*posicion para escribir el nuevo mensaje*/
    int                 nextout; /*posicion desde donde se lee el mensaje*/
    int                 free_slots; /*cantidad de espacio libre del buffer*/
} buffer_t;

int main(){
    buffer_t buff;
    //inicializamos el buffer
    buff.free_slots = BUFF_SIZE;
    buff.nextin = 0;
    buff.nextout = 0;
    pthread_cond_init(&buff.slot_available, NULL);
    pthread_cond_init(&buff.message_available, NULL);
    pthread_mutex_init(&buff.mutex, NULL);

    //creamos los dos threads
    pthread_t prod;
    pthread_t cons;

    if(pthread_create(&prod, NULL, productor, (void*)&buff) != 0){
            puts("Error creando al productor\n");
            return(EXIT_FAILURE);
    }

    if(pthread_create(&cons, NULL, consumidor, (void*)&buff) != 0){
            puts("Error creando al consumidor");
            return(EXIT_FAILURE);
    }

    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
    
    pthread_cond_destroy(&buff.slot_available);
    pthread_cond_destroy(&buff.message_available);
    pthread_mutex_destroy(&buff.mutex);    
    
    return(EXIT_SUCCESS);
}

void* productor(void* _data){
    static n = 1;
    buffer_t* buffer = (buffer_t*) _data;
    srand(time(NULL));
    
    while(1){
        /*obtenemos el buffer*/
        pthread_mutex_lock(&buffer->mutex);
        /*si no hay espacio libre en el buffer, esperamos a que lo haya*/
        while(buffer->free_slots == 0){
		    pthread_cond_wait(&buffer->slot_available, &buffer->mutex);
	    }
        
        /*insertamos un mensaje*/
        printf("escribiendo mensaje\n");
        buffer->buf[buffer->nextin] = n++;
        buffer->nextin = (buffer->nextin + 1) % BUFF_SIZE;
        buffer->free_slots--;
        
	    pthread_cond_signal(&buffer->message_available);
        pthread_mutex_unlock(&buffer->mutex);
        
        //esperamos un tiempo random para reescribir un nuevo msj
        sleep(rand()%3);
    }

    return NULL;
}

void* consumidor(void* _data){
    buffer_t* buffer = (buffer_t*) _data;
    srand(time(NULL)+123);
    
    while(1){
        /*obtenemos el buffer*/
        pthread_mutex_lock(&buffer->mutex);
        /*si no hay mensajes en el buffer, esperamos a que lo haya*/
        while(buffer->free_slots == BUFF_SIZE){
		    pthread_cond_wait(&buffer->message_available, &buffer->mutex);
	    }
        
        /*leemos un mensaje*/
        printf("consumiendo mensaje...");
        int message = buffer->buf[buffer->nextout];
        
        printf("%d\n", message);
        
        buffer->nextout = (buffer->nextout + 1) % BUFF_SIZE;
        buffer->free_slots++;
        
	    pthread_cond_signal(&buffer->slot_available);
        pthread_mutex_unlock(&buffer->mutex);
        
        //esperamos un tiempo random para reiniciar la consumision
        sleep(rand()%3);
    }
    return NULL;
}
